package com.superb.common.nacos;

import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.cloud.nacos.discovery.NacosDiscoveryClient;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.superb.common.nacos.service.SuperbNacosService;
import com.superb.common.utils.InnerIpUtils;
import com.superb.common.utils.IpUtils;
import com.superb.common.utils.NacosServerUtils;
import com.superb.common.utils.SpringContextUtils;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Executor;

/**
 * nacos服务监听
 * 监听需在springContextUtils注册之后，才好获取nacos服务实现
 * @Author: ajie
 * @CreateTime: 2024-05-15 15:04
 */
@Slf4j
@Configuration
@DependsOn("springContextUtils")
@ConditionalOnBean(name = "nacosServiceManager")
public class SuperbNacosListener implements DisposableBean {

    private List<SuperbNacosService> superbNacosServices = SpringContextUtils.getBeans(SuperbNacosService.class);

    /**
     * nacos配置中心管理
     */
    private NacosConfigManager nacosConfigManager = SpringContextUtils.getBean(NacosConfigManager.class);
    /**
     * nacos配置中心配置
     */
    private NacosConfigProperties nacosConfigProperties = SpringContextUtils.getBean(NacosConfigProperties.class);

    /**
     * nacos注册中心管理
     */
    private NacosServiceManager nacosServiceManager = SpringContextUtils.getBean(NacosServiceManager.class);
    /**
     * nacos注册中心配置
     */
    private NacosDiscoveryProperties nacosDiscoveryProperties = SpringContextUtils.getBean(NacosDiscoveryProperties.class);

    /**
     * NacosDiscoveryClient
     */
    private NacosDiscoveryClient nacosDiscoveryClient = SpringContextUtils.getBean(NacosDiscoveryClient.class);
    /**
     * 订阅服务Set
     */
    private CopyOnWriteArraySet<String> subscribeServiceNameSet = new CopyOnWriteArraySet<>();
    /**
     * 有效服务节点Map
     */
    private Map<String, Map<String, Set<Integer>>> serviceNodeList = new HashMap<>(16);

    /**
     * 跟随项目启动
     *
     * @throws NacosException
     */
    @PostConstruct
    public void postConstruct() throws NacosException {
        // 本地IP设置,自动进入内部IP列表
        InnerIpUtils.setLocalIp(this.nacosDiscoveryProperties.getIp());
        InnerIpUtils.add(IpUtils.LOCALHOST_IP4);
        // 遍历nacos服务并订阅
        this.serviceSubscribeFor(true);
        // 监听配置变化
        String dateId = this.nacosConfigProperties.getPrefix() + "-" + this.nacosConfigProperties.getGroup() + "." + this.nacosConfigProperties.getFileExtension();
        String group = this.nacosConfigProperties.getGroup();
        List<NacosConfigProperties.Config> configs = this.nacosConfigProperties.getSharedConfigs();
        configs.add(0, new NacosConfigProperties.Config(dateId, group, this.nacosConfigProperties.isRefreshEnabled()));
        for (NacosConfigProperties.Config config : configs) {
            dateId = config.getDataId();
            group = config.getGroup();
            this.configListener(dateId, group, true);
        }
    }

    /**
     * 遍历nacos服务
     */
    private void serviceSubscribeFor(boolean subscribe) throws NacosException {
        List<String> services = this.nacosDiscoveryClient.getServices();
        for (String serviceName : services) {
            this.serviceSubscribe(serviceName, subscribe);
        }
    }

    /**
     * 订阅服务
     *
     * @param serviceName 服务名称
     * @param subscribe   是否订阅
     * @throws NacosException
     */
    private void serviceSubscribe(String serviceName, boolean subscribe) throws NacosException {
        if (subscribe) {
            if (!this.subscribeServiceNameSet.contains(serviceName)) {
                log.info(MessageFormat.format("订阅服务{0}", serviceName));
                this.subscribeServiceNameSet.add(serviceName);
                this.nacosServiceManager.getNamingService(this.nacosDiscoveryProperties.getNacosProperties()).subscribe(serviceName, this.nacosDiscoveryProperties.getGroup(), event -> {
                    this.superbNacosServices.forEach(beeNacosService -> beeNacosService.serviceChange(serviceName, event));
                    this.serviceNodeListener(serviceName);
                });
            }
        } else {
            log.info(MessageFormat.format("取消订阅服务{0}", serviceName));
            this.subscribeServiceNameSet.remove(serviceName);
            this.nacosServiceManager.getNamingService(this.nacosDiscoveryProperties.getNacosProperties()).unsubscribe(serviceName, this.nacosDiscoveryProperties.getGroup(), event -> {
                this.superbNacosServices.forEach(beeNacosService -> beeNacosService.serviceChange(serviceName, event));
                this.serviceNodeListener(serviceName);
            });
        }
    }

    /**
     * 服务节点列表监听
     *
     * @param serviceName
     */
    private void serviceNodeListener(String serviceName) {
        synchronized (serviceName.intern()) {
            String serverPort = SpringContextUtils.getEnvironmentProperty("server.port");
            String applicationName = SpringContextUtils.getEnvironmentProperty("spring.application.name");
            String serverIp = InnerIpUtils.getLocalIp();
            List<ServiceInstance> instances = this.nacosDiscoveryClient.getInstances(serviceName);
            Map<String, Set<Integer>> nodeMap = new HashMap<>(16);
            for (ServiceInstance instance : instances) {
                String host = instance.getHost();
                int port = instance.getPort();
                if (nodeMap.containsKey(host)) {
                    Set<Integer> ports = nodeMap.get(host);
                    ports.add(port);
                } else {
                    Set<Integer> protSet = new HashSet<>(16);
                    protSet.add(port);
                    nodeMap.put(host, protSet);
                }
                InnerIpUtils.add(host);
            }
            for (Map.Entry<String, Set<Integer>> nodeMapEntry : nodeMap.entrySet()) {
                String host = nodeMapEntry.getKey();
                Set<Integer> portSet = nodeMapEntry.getValue();
                if (this.serviceNodeList.containsKey(serviceName)) {
                    Map<String, Set<Integer>> nodes = this.serviceNodeList.get(serviceName);
                    if (nodes.containsKey(host)) {
                        Set<Integer> ports = nodes.get(host);
                        for (Integer port : portSet) {
                            if (!ports.contains(port)) {
                                ports.add(port);
                                if (serverPort.equals("" + port) && serverIp.equals(host)) {
                                    NacosServerUtils.setEnable(true);
                                    this.superbNacosServices.forEach(beeNacosService -> beeNacosService.currentNodeState("register"));
                                }
                                this.superbNacosServices.forEach(beeNacosService -> beeNacosService.serviceNodeChange(serviceName, host, port, "register"));
                            }
                        }
                    } else {
                        nodes.put(host, portSet);
                        eachRegister(serviceName, serverPort, serverIp, host, portSet);
                    }
                } else {
                    Map<String, Set<Integer>> nodes = new HashMap<>(16);
                    nodes.put(host, portSet);
                    this.serviceNodeList.put(serviceName, nodes);
                    if (applicationName.equals(serviceName)) {
                        this.superbNacosServices.forEach(beeNacosService -> beeNacosService.currentNodeState("cancel"));
                    }
                    eachRegister(serviceName, serverPort, serverIp, host, portSet);
                }
            }
            if (this.serviceNodeList.containsKey(serviceName)) {
                Map<String, Set<Integer>> nodes = new HashMap<>(16);
                nodes.putAll(this.serviceNodeList.get(serviceName));
                for (Map.Entry<String, Set<Integer>> nodeMapEntry : nodes.entrySet()) {
                    String host = nodeMapEntry.getKey();
                    Set<Integer> portSet = new HashSet<>(nodeMapEntry.getValue());
                    if (nodeMap.containsKey(host)) {
                        Set<Integer> ports = nodeMap.get(host);
                        for (Integer port : portSet) {
                            if (!ports.contains(port)) {
                                if (serverPort.equals("" + port) && serverIp.equals(host)) {
                                    NacosServerUtils.setEnable(false);
                                    this.superbNacosServices.forEach(beeNacosService -> beeNacosService.currentNodeState("cancel"));
                                }
                                this.superbNacosServices.forEach(beeNacosService -> beeNacosService.serviceNodeChange(serviceName, host, port, "cancel"));
                                this.serviceNodeList.get(serviceName).get(host).remove(port);
                            }
                        }
                    } else {
                        for (Integer port : portSet) {
                            if (serverPort.equals("" + port) && serverIp.equals(host)) {
                                NacosServerUtils.setEnable(false);
                                this.superbNacosServices.forEach(beeNacosService -> beeNacosService.currentNodeState("cancel"));
                            }
                            this.superbNacosServices.forEach(beeNacosService -> beeNacosService.serviceNodeChange(serviceName, host, port, "cancel"));
                        }
                        this.serviceNodeList.get(serviceName).remove(host);
                    }
                }
            }
        }
    }

    private void eachRegister(String serviceName, String serverPort, String serverIp, String host, Set<Integer> portSet) {
        for (Integer port : portSet) {
            if (serverPort.equals("" + port) && serverIp.equals(host)) {
                NacosServerUtils.setEnable(true);
                this.superbNacosServices.forEach(beeNacosService -> beeNacosService.currentNodeState("register"));
            }
            this.superbNacosServices.forEach(beeNacosService -> beeNacosService.serviceNodeChange(serviceName, host, port, "register"));
        }
    }

    /**
     * 监听配置
     *
     * @param dateId
     * @param group
     * @param listener
     * @throws NacosException
     */
    private void configListener(String dateId, String group, boolean listener) throws NacosException {
        if (listener) {
            log.info(MessageFormat.format("监听配置{0},分组{1}", dateId, group));
            this.nacosConfigManager.getConfigService().addListener(dateId, group, new Listener() {
                @Override
                public Executor getExecutor() {
                    return null;
                }

                @Override
                public void receiveConfigInfo(String configInfo) {
                    SuperbNacosListener.this.superbNacosServices.forEach(beeNacosService -> beeNacosService.configChange(dateId, configInfo));
                }
            });
        } else {
            log.info(MessageFormat.format("取消监听配置{0},分组{1}", dateId, group));
            this.nacosConfigManager.getConfigService().removeListener(dateId, group, new Listener() {
                @Override
                public Executor getExecutor() {
                    return null;
                }

                @Override
                public void receiveConfigInfo(String configInfo) {
                    SuperbNacosListener.this.superbNacosServices.forEach(beeNacosService -> beeNacosService.configChange(dateId, configInfo));
                }
            });
        }
    }


    /**
     * 移除相关监听订阅
     *
     * @throws NacosException
     */
    public void removeListener() throws NacosException {
        // 遍历nacos服务并取消订阅
        this.serviceSubscribeFor(false);
        String dateId = this.nacosConfigProperties.getPrefix() + "-" + this.nacosConfigProperties.getGroup() + "." + this.nacosConfigProperties.getFileExtension();
        String group = this.nacosConfigProperties.getGroup();
        List<NacosConfigProperties.Config> configs = this.nacosConfigProperties.getSharedConfigs();
        configs.add(0, new NacosConfigProperties.Config(dateId, group, this.nacosConfigProperties.isRefreshEnabled()));
        for (NacosConfigProperties.Config config : configs) {
            dateId = config.getDataId();
            group = config.getGroup();
            this.configListener(dateId, group, false);
        }
    }

    @Override
    public void destroy() throws Exception {

    }
}
